package game.square;

import static org.junit.Assert.*;

import game.dungeon.position.Direction;
import game.square.temperature.Temperature;
import game.square.temperature.TemperatureScale;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

import org.junit.*;



public class SquareTest {
	
	private SquareImpl squareTemp50Cel ,slipperyMaterialSquare, northBorderedSquare, terminatedSquare,
    borderedInAllDirectionsSquare, squareTemp50CelHum36,squareTemp50CelHum37, squareTemp50CelHum43, squareTemp50CelHum44;
	private BigDecimal bigDecimal10;
	private BigDecimal bigDecimal10_10;
	private BigDecimal bigDecimal100;
	/**
	 * Set up a mutable test fixture.
	 * 
	 * @post   The variable squareTemp50 references a new
	 *         square with temperature 50.
	 */
	@Before
	public void setUp() throws Exception {
		bigDecimal10 = new BigDecimal(BigInteger.valueOf(1000),2 );
		bigDecimal10_10 = BigDecimal.valueOf(1010,2);
		bigDecimal100 = new BigDecimal(BigInteger.valueOf(10000),2);
		squareTemp50Cel = new CommonSquare(50,TemperatureScale.CELCIUS, bigDecimal10, false, null, new ArrayList<Square>());
		slipperyMaterialSquare = new CommonSquare(50,TemperatureScale.CELCIUS, bigDecimal10, true,null, new ArrayList<Square>());
		northBorderedSquare = new CommonSquare();
		terminatedSquare = new CommonSquare();
		terminatedSquare.terminate();
		squareTemp50CelHum36 = new CommonSquare(50, TemperatureScale.CELCIUS, BigDecimal.valueOf(3600,2), false, null, new ArrayList<Square>());
		squareTemp50CelHum37 = new CommonSquare(50, TemperatureScale.CELCIUS, BigDecimal.valueOf(3700,2), false, null, new ArrayList<Square>());
		squareTemp50CelHum43 = new CommonSquare(50, TemperatureScale.CELCIUS, BigDecimal.valueOf(4300,2), false, null, new ArrayList<Square>());
		squareTemp50CelHum44 = new CommonSquare(50, TemperatureScale.CELCIUS, BigDecimal.valueOf(4400,2), false, null, new ArrayList<Square>());
		borderedInAllDirectionsSquare = new CommonSquare();
		addAllDirections(borderedInAllDirectionsSquare);
		
	
	}


	@After
	public void tearDown() throws Exception {
	}
		
	@Test
	public final void extendedConstructor_LegalCase() {
		Square newSquare = new CommonSquare(0, TemperatureScale.CELCIUS, bigDecimal10, true, null, new ArrayList<Square>());
		assertEquals(0, newSquare.getTemperature().getTemperatureValue(TemperatureScale.CELCIUS));
		assertEquals(bigDecimal10,newSquare.getHumidity());
		assertEquals(true, newSquare.isSlipperyMaterial());
		assertTrue(newSquare.hasProperBorders());
	}
	
	@Test (expected = AssertionError.class)
	public final void extendedConstructor_TempScaleIllegalLegalCase() {
		new CommonSquare(0, null, bigDecimal10, true, null,new ArrayList<Square>());
	}
	
	@Test (expected = AssertionError.class)
	public final void extendedConstructor_HumidityIllegalLegalCase() {
		new CommonSquare(0, TemperatureScale.CELCIUS, null, true, null,new ArrayList<Square>());
	}
	
	@Test
	public final void defaultConstructor_SingleCase() {
		Square newSquare = new CommonSquare();
		assertEquals(20, newSquare.getTemperature().getTemperatureValue(TemperatureScale.CELCIUS));
		assertEquals(BigDecimal.valueOf(1500,2),newSquare.getHumidity());
		assertEquals(false, newSquare.isSlipperyMaterial());
		assertTrue(newSquare.hasProperBorders());
	}
	
	@Test
	public final void getColdDamage_SingleCase(){
		assertEquals(new CommonSquare(-16, TemperatureScale.CELCIUS,
				bigDecimal10, false, null, new ArrayList<Square>())
				.getColdDamage(), 1);
		assertEquals(new CommonSquare(-15, TemperatureScale.CELCIUS,
				bigDecimal10, false, null, new ArrayList<Square>())
				.getColdDamage(), 1);
		assertEquals(new CommonSquare(-14, TemperatureScale.CELCIUS,
				bigDecimal10, false, null, new ArrayList<Square>())
				.getColdDamage(), 0);
		assertEquals(new CommonSquare(16, TemperatureScale.CELCIUS,
				bigDecimal10, false, null, new ArrayList<Square>())
				.getColdDamage(), 0);
	}
	
	@Test
	public final void getHeatDamage_SingleCase(){
		int boundaryTemp = SquareImpl.getMinTempWithHeatDamage();
		assertEquals(new CommonSquare(boundaryTemp + 1,
				TemperatureScale.CELCIUS, bigDecimal10, false, null,
				new ArrayList<Square>()).getHeatDamage(), 1);
		assertEquals(new CommonSquare(boundaryTemp, TemperatureScale.CELCIUS,
				bigDecimal10, false, null, new ArrayList<Square>())
				.getHeatDamage(), 1);
		assertEquals(new CommonSquare(boundaryTemp - 1,
				TemperatureScale.CELCIUS, bigDecimal10, false, null,
				new ArrayList<Square>()).getHeatDamage(), 0);
		assertEquals(new CommonSquare(-(boundaryTemp + 1),
				TemperatureScale.CELCIUS, bigDecimal10, false, null,
				new ArrayList<Square>()).getHeatDamage(), 0);
	}
	
	@Test
	public final void getRustDamage_SingleCase(){
		assertEquals(0, squareTemp50CelHum36.getRustDamage());
		assertEquals(1, squareTemp50CelHum37.getRustDamage());
		assertEquals(1, squareTemp50CelHum43.getRustDamage());
		assertEquals(2, squareTemp50CelHum44.getRustDamage());
	}
	
	@Test 
	public final void isSlippery_trueCase() {
		assertEquals(true, slipperyMaterialSquare.isSlippery());
		CommonSquare comSquareTemp50Cel = (CommonSquare) squareTemp50Cel;
		comSquareTemp50Cel.setTemperature(new Temperature(-1,-200,5000,TemperatureScale.CELCIUS));
		comSquareTemp50Cel.setHumidity(bigDecimal10_10);
		assertEquals(true, comSquareTemp50Cel.isSlippery());
		comSquareTemp50Cel.setTemperature(new Temperature(0,-200,5000,TemperatureScale.CELCIUS));
		comSquareTemp50Cel.setHumidity(bigDecimal100);
		assertEquals(true, comSquareTemp50Cel.isSlippery());
	}
	
	@Test
	public final void isSlippery_falseCase() {
		CommonSquare comSquareTemp50Cel = (CommonSquare) squareTemp50Cel;
		comSquareTemp50Cel.setHumidity(bigDecimal10_10);
		assertEquals(false, comSquareTemp50Cel.isSlippery());
		comSquareTemp50Cel.setHumidity(bigDecimal10);
		comSquareTemp50Cel.setTemperature(new Temperature(-1,-200,5000,TemperatureScale.CELCIUS));
		assertEquals(false, comSquareTemp50Cel.isSlippery());
	}
	
	@Test 
	public final void getInhabitability_SingleCase() {
		CommonSquare comSquareTemp50Cel = (CommonSquare) squareTemp50Cel;
		comSquareTemp50Cel.setHumidity(bigDecimal10_10);
		// works only if you don't change the calculations regarding heat and cold damage
		assertEquals("", -0.104886129, squareTemp50Cel.getInhabitability(), 0.01);
	}
	
	@Test
	public final void terminate_NormalCase(){
		Border border = northBorderedSquare.getBorderInDirection(Direction.NORTH);
		//border.getNeighborSquareFrom(northBorderedSquare) == null
		northBorderedSquare.terminate();
		assertTrue(northBorderedSquare.isTerminated());
		assertTrue(northBorderedSquare.getBorders().isEmpty());
		assertTrue(border.isTerminated());
	}
	
	@Test
	public final void terminate_AlreadyTerminated(){
		terminatedSquare.terminate();
		assertTrue(terminatedSquare.isTerminated());
		assertTrue(terminatedSquare.getBorders().isEmpty());	
	}
	
	@Test
	public final void hasAsBorder_TrueCase(){
		for (Border border : northBorderedSquare.getBorders().values())
			assertTrue(northBorderedSquare.hasAsBorder(border));
	}
	
	@Test
	public final void hasAsBorder_FalseCase(){
		assertFalse (northBorderedSquare.hasAsBorder(new Door()));
	}
	
	@Test
	public final void canHaveAsBorder_TrueCase(){
		assertTrue(northBorderedSquare.canHaveAsBorder(new Wall(false)));
	}
	
	@Test
	public final void canHaveAsBorder_FalseCase(){
		assertFalse(northBorderedSquare.canHaveAsBorder(null));
		assertFalse(borderedInAllDirectionsSquare.canHaveAsBorder(new Wall(false)));
		Border border = new Wall(false);
		assertFalse(terminatedSquare.canHaveAsBorder(border));
		border.terminate();
		assertFalse(terminatedSquare.canHaveAsBorder(border));	
	}
	@Test
	public final void canHaveAsBorderInDirection_TrueCase(){
		assertTrue(northBorderedSquare.canHaveAsBorderInDirection(new Wall(false),Direction.SOUTH));
		
	}
	
	@Test
	public final void canHaveAsBorderInDirection_FalseCase(){
		assertFalse(northBorderedSquare.canHaveAsBorderInDirection(new Wall(false),null));
		assertFalse(northBorderedSquare.canHaveAsBorderInDirection(new Door(),Direction.FLOOR));
		
	}
	
	@Test
	public final void hasProperBorders_NotTerminated(){
		assertTrue(northBorderedSquare.hasProperBorders());
	}
	
	@Test
	public final void hasProperBorders_Terminated(){
		assertTrue(terminatedSquare.hasProperBorders());
	}
	
	@Test
	public final void isPlaceableBorderInDirection_LegalCase(){
		assertTrue(northBorderedSquare.isPlaceableBorderInDirection(new Door(), Direction.CEILING));
	}
	
	@Test
	public final void isPlaceableBorderInDirection_IllegalCase(){
		Border border = new Door();
		new CommonSquare().addBorderInDirection(border, Direction.CEILING);
		assertFalse(northBorderedSquare.isPlaceableBorderInDirection(border, Direction.CEILING));
		Square twoDoorsSquare = new CommonSquare();
		twoDoorsSquare.addBorderInDirection(new Door(), Direction.SOUTH);
		twoDoorsSquare.addBorderInDirection(new Door(), Direction.WEST);
		assertFalse(twoDoorsSquare.isPlaceableBorderInDirection(new Door(),Direction.CEILING));
	}
	
	@Test
	public final void addBorderInDirection_LegalCase(){
		northBorderedSquare.addBorderInDirection(new Door(), Direction.SOUTH);
	}
	
	@Test (expected = IllegalStateException.class)
	public final void addBorderInDirection_IllegalCase(){
		northBorderedSquare.addBorderInDirection(new Door(), Direction.NORTH);
	}
	
	@Test
	public final void addNeighborInDirection_LegalCase(){
		SquareImpl neighboredSquare = new CommonSquare();
		northBorderedSquare.addNeighborInDirection(neighboredSquare, Direction.NORTH);
		assertEquals(neighboredSquare, northBorderedSquare.getNeighborInDirection(Direction.NORTH));
	}
	
	@Test (expected = IllegalArgumentException.class)
	public final void addNeighborInDirection_IllegalCaseNonEffectiveSquare(){
		northBorderedSquare.addNeighborInDirection(null, Direction.NORTH);	
	}
	
	@Test (expected = IllegalArgumentException.class)
	public final void addNeighborInDirection_IllegalCaseNonEffectiveDirection(){
		northBorderedSquare.addNeighborInDirection(new CommonSquare(), null);
	}
	
	@Test (expected = IllegalArgumentException.class)
	public final void addNeighborInDirection_IllegalCaseAlreadyBorderedInDirection(){
		northBorderedSquare.addNeighborInDirection(new CommonSquare(), Direction.NORTH);
		northBorderedSquare.addNeighborInDirection(new CommonSquare(), Direction.NORTH);
	}
	
	@Test (expected = IllegalArgumentException.class)
	public final void addNeighborInDirection_IllegalCaseArgumentAlreadyBorderInDirection(){
		northBorderedSquare.addNeighborInDirection(new CommonSquare(), Direction.NORTH);
		new CommonSquare().addNeighborInDirection(northBorderedSquare, Direction.SOUTH);
	}
	
	@Test 
	public final void removeBorderInDirection_LegalCase(){
		northBorderedSquare.removeBorderInDirection(Direction.NORTH);
		SquareImpl neighborSquare= new CommonSquare();
		northBorderedSquare.addNeighborInDirection(neighborSquare,Direction.SOUTH);
		assertEquals(null,northBorderedSquare.getBorderInDirection(Direction.NORTH));
		assertEquals(Door.class, neighborSquare.getBorderInDirection(Direction.NORTH).getClass());
	}
	
	@Test (expected = AssertionError.class)
	public final void removeBorderInDirection_IllegalCase(){
		northBorderedSquare.removeBorderInDirection(Direction.SOUTH);
	}
	
	private void addAllDirections(Square square) {
		square.addBorderInDirection(new Wall(false),Direction.EAST);
		square.addBorderInDirection(new Wall(false),Direction.SOUTH);
		square.addBorderInDirection(new Wall(false),Direction.WEST);
		square.addBorderInDirection(new Wall(false),Direction.CEILING);
		square.addBorderInDirection(new Wall(false),Direction.FLOOR);		
	}

	
	
}
